package com.smile.cloud.config;

import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.smile.cloud.authorization.AuthorizationManager;
import com.smile.cloud.authorization.DynamicSecurityService;
import com.smile.cloud.component.RestAuthenticationEntryPoint;
import com.smile.cloud.component.RestfulAccessDeniedHandler;
import com.smile.cloud.common.base.constant.AuthConstant;
import com.smile.cloud.filter.IgnoreUrlsRemoveJwtFilter;
import com.smile.cloud.mbg.base.model.SysResource;
import com.smile.cloud.mbg.base.model.SysRole;
import com.smile.cloud.mbg.base.model.SysRoleResource;
import com.smile.cloud.mbg.base.service.SysResourceService;
import com.smile.cloud.mbg.base.service.SysRoleResourceService;
import com.smile.cloud.mbg.base.service.SysRoleService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 资源服务器配置
 * Created smile on  2020/6/19.
 */
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
    private final SysRoleService sysRoleService;
    private final SysResourceService sysResourceService;
    private final SysRoleResourceService sysRoleResourceService;

    private final AuthorizationManager authenticationManager;
    private final IgnoreUrlsConfig ignoreUrlsConfig;
    private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
        //自定义处理JWT请求头过期或签名错误的结果
        http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
        //对白名单路径，直接移除JWT请求头
        http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
        http.authorizeExchange()
                .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(), String.class)).permitAll()//白名单配置
                .anyExchange().access(authenticationManager)//鉴权管理器配置
                .and().exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权
                .authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证
                .and().csrf().disable();
        return http.build();
    }

    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }

    /**
     * 自定义实现资源角色加载
     *
     * @return
     */
    @Bean
    public DynamicSecurityService dynamicSecurityService() {
        return new DynamicSecurityService() {
            @Override
            public HashMap<String, Collection<ConfigAttribute>> loadDataSource() {
                HashMap<String, Collection<ConfigAttribute>> map = new HashMap<>(16);
                //获取所有资源与角色
                List<SysResource> resourcesList = sysResourceService.list();
                for (SysResource resource : resourcesList) {//遍历资源
                    QueryWrapper<SysRoleResource> query = new QueryWrapper<>();
                    query.lambda().eq(SysRoleResource::getResourceId, resource.getResourceId());
                    //查询资源对应的所有角色
                    List<SysRoleResource> roleResourcesList = sysRoleResourceService.list(query);
                    if (roleResourcesList != null && roleResourcesList.size() != 0) {
                        //获取对应的角色
                        List<Integer> roleIdList = roleResourcesList.stream().map(SysRoleResource::getRoleId).collect(Collectors.toList());
                        List<SysRole> xfpRoleList = sysRoleService.listByIds(roleIdList);

                        List<ConfigAttribute> roleList = new ArrayList<>();
                        for (SysRole sysRole : xfpRoleList) {
                            ConfigAttribute role = new org.springframework.security.access.SecurityConfig("role_" + sysRole.getSaasCode() + "_" + sysRole.getRoleNo());
                            roleList.add(role);
                        }
                        if (roleList.size() > 0) {
                            map.put(resource.getUrl(), roleList);
                        }
                    }
                }
                return map;
            }
        };
    }

}
